package com.quan.commons.utils;

import com.google.common.collect.Maps;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

/**
 * AOP及EL表达式工具集
 *
 * @author hp
 * @since 2020-12-09
 */
public class AopAndElUtils {

    /**
     * el表达式解析器，线程安全
     */
    private static ExpressionParser expressionParser = new SpelExpressionParser();

    private static ConcurrentMap<String, Expression> expressionMap = Maps.newConcurrentMap();

    private static final String SECRET_TIP = "******";

    private static final String PASSWORD_KEYWORD = "password";

    private static final String TOKEN_KEYWORD = "token";

    /**
     * 构造EL表达式上下文
     *
     * @param point  连接点
     * @param result 连接点执行结果
     * @return org.springframework.expression.EvaluationContext EL上下文
     */
    public static EvaluationContext buildContext(ProceedingJoinPoint point, Object result) {
        EvaluationContext context = buildContext(point);
        context.setVariable("result", result);
        return context;
    }

    /**
     * 构造EL表达式上下文(不含执行结果)
     *
     * @param point 连接点
     * @return org.springframework.expression.EvaluationContext EL上下文
     */
    public static EvaluationContext buildContext(ProceedingJoinPoint point) {
        EvaluationContext context = new StandardEvaluationContext();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) point.getSignature();
        String[] parameterNames = ms.getParameterNames();
        Object[] args = point.getArgs();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        return context;
    }

    /**
     * 获取连接点上的注解
     *
     * @param point          连接点
     * @param annotationType 注解类型
     * @param <T>            注解类
     * @return T 注解
     */
    public static <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint point, Class<T> annotationType) {
        //获取类的字节码对象，通过字节码对象获取方法信息
        Class<?> targetCls = point.getTarget().getClass();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) point.getSignature();
        //获取目标方法上的注解指定的操作名称
        Method targetMethod = null;
        try {
            targetMethod = targetCls.getDeclaredMethod(
                    ms.getName(),
                    ms.getParameterTypes()
            );
        } catch (NoSuchMethodException e) {
            return null;
        }
        return targetMethod.getAnnotation(annotationType);
    }

    /**
     * 获取连接点上的注解数组
     *
     * @param point 连接点
     * @return java.lang.annotation.Annotation 注解数组
     */
    public static Annotation[] getMethodAnnotations(ProceedingJoinPoint point) {
        //获取类的字节码对象，通过字节码对象获取方法信息
        Class<?> targetCls = point.getTarget().getClass();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) point.getSignature();
        //获取目标方法上的注解指定的操作名称
        Method targetMethod = null;
        try {
            targetMethod = targetCls.getDeclaredMethod(
                    ms.getName(),
                    ms.getParameterTypes()
            );
        } catch (NoSuchMethodException e) {
            return null;
        }
        return targetMethod.getAnnotations();
    }

    /**
     * 获取连接点上的注解数组
     *
     * @param point 连接点
     * @return java.lang.Class 连接点上的注解
     */
    public static Class<?> getMethodReturnType(ProceedingJoinPoint point) {
        //获取类的字节码对象，通过字节码对象获取方法信息
        Class<?> targetCls = point.getTarget().getClass();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) point.getSignature();
        //获取目标方法上的注解指定的操作名称
        Method targetMethod = null;
        try {
            targetMethod = targetCls.getDeclaredMethod(
                    ms.getName(),
                    ms.getParameterTypes()
            );
        } catch (NoSuchMethodException e) {
            return null;
        }
        return targetMethod.getReturnType();
    }

    /**
     * 获取切面类上的注解
     *
     * @param point          连接点
     * @param annotationType 注解类型
     * @param <T>            注解类
     * @return java.lang.annotation.Annotation 注解
     */
    public static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint point, Class<T> annotationType) {
        //获取类的字节码对象，通过字节码对象获取方法信息
        Class<?> targetCls = point.getTarget().getClass();
        return targetCls.getAnnotation(annotationType);
    }

    /**
     * 根据原始表达式获得表达式对象
     *
     * @param key 原始表达式
     * @return org.springframework.expression.Expression 表达式对象
     */
    public static Expression getExpressionByKey(String key) {
        if (expressionMap.containsKey(key)) {
            return expressionMap.get(key);
        }
        Expression expression = expressionParser.parseExpression(key);
        expressionMap.put(key, expression);
        return expression;
    }

    /**
     * 安全获取连接点上的方法入参
     * 自动屏蔽敏感词
     *
     * @param point 连接点
     * @return 方法入参
     */
    public static Map getParametersSafe(ProceedingJoinPoint point) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        String[] parameterNames = ms.getParameterNames();
        Object[] args = point.getArgs();
        Map<String, Object> paramMap = Maps.newHashMap();
        for (int i = 0; i < parameterNames.length; i++) {
            // 过滤涉密参数
            if (parameterNames[i].toLowerCase().contains(PASSWORD_KEYWORD)
                    || parameterNames[i].toLowerCase().contains(TOKEN_KEYWORD)) {
                paramMap.put(parameterNames[i], SECRET_TIP);
            } else {
                paramMap.put(parameterNames[i], args[i]);
            }
        }
        return paramMap;
    }

    /**
     * 获取连接点上的方法入参
     *
     * @param point 连接点
     * @return 方法入参
     */
    public static Map getParameters(ProceedingJoinPoint point) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        String[] parameterNames = ms.getParameterNames();
        Object[] args = point.getArgs();
        Map<String, Object> paramMap = Maps.newHashMap();
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        return paramMap;
    }
}
